#pragma once

#include "drogon/drogon.h"
#include "OCMain/FileUtil.h"
#include "OCServer/ServerUtil.h"
#include "WeatherUtil.h"

#include "Anal1km.h"
#include "Anal3km.h"
#include "Fcst1km.h"
#include "Fcst3km.h"

using Anal1kmData = drogon_model::sqlite3::Anal1km;
using Anal1kmDataList = std::vector<Anal1kmData>;
using Fcst1kmData = drogon_model::sqlite3::Fcst1km;
using Fcst1kmDataList = std::vector<Fcst1kmData>;

using Anal3kmData = drogon_model::sqlite3::Anal3km;
using Anal3kmDataList = std::vector<Anal3kmData>;
using Fcst3kmData = drogon_model::sqlite3::Fcst3km;
using Fcst3kmDataList = std::vector<Fcst3kmData>;

using namespace OCPP;

class WeatherDB
{
public:
	WeatherDB();
	~WeatherDB();

	template<class T>
	static bool existAnal(const T& data);

	template<class T>
	static size_t countAnal(String date);

	template<class T>
	static bool existFcst(const T& data);

	template<class T>
	static size_t countFcst(uint64 g);

	template<class T>
	static void insert(std::vector<T> list);

	template<class T>
	static void clear(String startTime, String endTime);

	template<class T>
	static Json::Value findAnal(String startTime, String endTime);
	template<class T>
	static Json::Value findFcst(String startTime, String endTime);

	static std::shared_mutex rwMutex;
	static String Path;
	static String DbName;
};

template<class T>
size_t WeatherDB::countAnal(String date)
{
	CORE_LOCK_RW_MUTEX_READ(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(DbName);
	try {
		drogon::orm::Mapper<T> mp(dbClient);
		StringStream sstream;
		sstream << T::Cols::_timestamp << ">=" << date << "000000 AND ";
		sstream << T::Cols::_timestamp << "<=" << date << "240000";
		drogon::orm::CustomSql customSql(sstream.str());
		return mp.count(drogon::orm::Criteria(customSql));
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}

	return 0;
}

template<class T>
bool WeatherDB::existAnal(const T& data)
{
	CORE_LOCK_RW_MUTEX_READ(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
	try {
		drogon::orm::Mapper<T> mp(dbClient);
		StringStream sstream;
		sstream << Fcst3kmData::Cols::_timestamp << "=" << data.getValueOfTimestamp();
		drogon::orm::CustomSql customSql(sstream.str());
		return mp.count(drogon::orm::Criteria(customSql)) > 0;
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}

	return false;
}


template<class T>
size_t WeatherDB::countFcst(uint64 g)
{
	CORE_LOCK_RW_MUTEX_READ(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
	try {
		drogon::orm::Mapper<T> mp(dbClient);
		StringStream sstream;
		sstream << T::Cols::_g << "=" << g;
		drogon::orm::CustomSql customSql(sstream.str());
		return mp.count(drogon::orm::Criteria(customSql));
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}

	return 0;
}

template<class T>
bool WeatherDB::existFcst(const T& data)
{
	CORE_LOCK_RW_MUTEX_READ(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
	try {
		drogon::orm::Mapper<T> mp(dbClient);
		StringStream sstream;
		sstream << T::Cols::_g << "=" << data.getValueOfG() << " AND "
			<< T::Cols::_timestamp << "=" << data.getValueOfTimestamp();
		drogon::orm::CustomSql customSql(sstream.str());
		return mp.count(drogon::orm::Criteria(customSql)) > 0;
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}

	return false;
}


template<class T>
void WeatherDB::insert(std::vector<T> list)
{
	if (list.empty())return;
	CORE_LOCK_RW_MUTEX_WRITE(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
	std::shared_ptr<drogon::orm::Transaction> transaction = dbClient->newTransaction();
	try {
		drogon::orm::Mapper<T> mp(transaction);
		for (const auto& data : list)
		{
			mp.insertFuture(data);
		}
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}
}

template<class T>
void WeatherDB::clear(String startTime,String endTime)
{
	uint64 startTimeStamp = std::atoll(String(startTime + "000000").c_str());
	uint64 endTimeStamp = std::atoll(String(endTime + "240000").c_str());
	CORE_LOCK_RW_MUTEX_WRITE(rwMutex);
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);
	try {
		StringStream sstream;
		sstream << T::Cols::_timestamp << "<=" << endTimeStamp << " AND ";
		sstream << T::Cols::_timestamp << ">=" << startTimeStamp;
		drogon::orm::CustomSql customSql(sstream.str());

		drogon::orm::Mapper<T> mp(dbClient);
		mp.deleteBy(drogon::orm::Criteria(customSql));
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
	}
}

template<class T>
Json::Value WeatherDB::findAnal(String startTime,String endTime)
{
	Json::Value resultObj;

	try {
		CORE_LOCK_RW_MUTEX_READ(rwMutex);
		drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);

		StringStream sstream;
		sstream << T::Cols::_timestamp << " >= " << startTime;

		if (endTime.length() == 14)
		{
			sstream << " AND "
				<< T::Cols::_timestamp << " <= " << endTime;
		}

		drogon::orm::CustomSql customSql(sstream.str());
		drogon::orm::Mapper<T> mp(dbClient);
		auto list = mp.findBy(drogon::orm::Criteria(customSql));
		for (auto data : list)
		{
			resultObj.append((Json::UInt64)data.getValueOfTimestamp());
		}
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
		LOG_ERROR << "unkown error\n";
	}

	return resultObj;
}

template<class T>
Json::Value WeatherDB::findFcst(String startTime, String endTime)
{
	Json::Value resultObj;

	try {
		CORE_LOCK_RW_MUTEX_READ(rwMutex);
		drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(WeatherDB::DbName);

		StringStream sstream;
		sstream << T::Cols::_timestamp << " >= " << startTime;

		if (endTime.length() == 14)
		{
			sstream << " AND "
				<< T::Cols::_timestamp << " <= " << endTime;
		}

		drogon::orm::CustomSql customSql(sstream.str());
		drogon::orm::Mapper<T> mp(dbClient);
		auto list = mp.findBy(drogon::orm::Criteria(customSql));

		std::map<uint64, T> m;
		for (auto data : list)
		{
			auto timestamp = (Json::UInt64)data.getValueOfTimestamp();
			auto it = m.find(timestamp);
			if (it == m.end())
			{
				m[timestamp] = data;
			}
			else if (it->second.getValueOfG() < data.getValueOfG())
			{
				it->second = data;
			}
		}

		for (auto [key, data] : m)
		{
			Json::Value v;
			v[T::Cols::_g] = (Json::UInt64)data.getValueOfG();
			v[T::Cols::_timestamp] = (Json::UInt64)data.getValueOfTimestamp();
			resultObj.append(v);
		}
	}
	catch (drogon::orm::DrogonDbException& ex) {
		LOG_ERROR << ex.base().what() << "\n";
	}
	catch (std::exception& ex) {
		LOG_ERROR << ex.what() << "\n";
	}
	catch (...) {
		LOG_ERROR << "unkown error\n";
	}

	return resultObj;
}